#include <ctime>
#include <iostream>
#include <sstream>
#include <string>
#include "misc/DEFS.H"
#include "CORE.H"
#include "dat/INI.H"

#if (__GNUC__)
#include <sys/time.h>
#include <iomanip>
struct timeval start,then;
#elif (_MSC_VER)
#define snprintf _snprintf
clock_t start;
clock_t then;
#endif

/*
Todo:   Remover essas variáveis globais
        Refactoring do Core e do Dat
*/
char Buf[BUF_SIZE];					// Buffer de texto global

CORE Core;

using namespace std;

int main( int argc, char* argv[] ) {

	Core.Log( LOGO, NEWLINE, NEWLINE );					// Imprime o nome do programa
	Core.GetInput( argc, argv );						// Carrega o nome do arquivo de entrada
	Core.Run();
	return 0;								// Esta linha nunca deverá ser executada
}

#if (__GNUC__)
inline void exportaTempo (REAL tempoTrabalhando,REAL tempoTotal)
{
    struct timeval atual;
    long etime,t_est;
     //Abaixo uma implementação de progresso e tempo estimado até o final
    gettimeofday(&atual, NULL);
    int time_elapsed_in_seconds = atual.tv_sec-then.tv_sec;
    if (time_elapsed_in_seconds>=1)
    {
        gettimeofday(&atual, NULL);

        /*float work = (Model.Time-trabalho);
        etime=atual.tv_sec-start.tv_sec-1;
        float trabs = HarmonicMean(trabalho/etime,work);
        int estimado=(Dat.RecTime-Model.Time)/trabs;
        */
        etime=atual.tv_sec-start.tv_sec;
        t_est=(etime*tempoTrabalhando)/tempoTotal;
        int estimado=t_est-etime;

        std::cout << "\r   " << 100.0f * tempoTotal / tempoTrabalhando << "%                ";
        std::cout << (etime)/3600 << 'h'<< ((etime)/60)%60 << 'm'<< (etime)%60 << "s             ";
        std::cout <<(estimado)/3600 << 'h'<< ((estimado)/60)%60 << 'm'<< (estimado)%60 << "s     ";

        fflush(stdout);
        then=atual;
        //trabalho=Model.Time;
    }
}
#else
inline void exportaTempo (REAL tempoTrabalhando,REAL tempoTotal)
{
    clock_t now(clock());
    int eltime = (now-then)/CLOCKS_PER_SEC;
    if ((eltime)>=1)
    {
        int tel_time = (double)(now-start)/CLOCKS_PER_SEC;
        int t_est=(tel_time*tempoTrabalhando)/tempoTotal;
        int estimado=t_est-tel_time;

        std::cout << "\r    " << 100.0f * tempoTotal / tempoTrabalhando << "%  \t\t";
        std::cout << (tel_time)/3600 << 'h'<< ((tel_time)/60)%60 << 'm'<< (tel_time)%60 << "s\t\t ";
        std::cout <<(estimado)/3600 << 'h'<< ((estimado)/60)%60 << 'm'<< (estimado)%60 << "s     ";
        then=now;
    }
}
#endif

inline void salvaTempo (int numThreads)
{
    #if (__GNUC__)
    double etime;
    long seconds, useconds;
	struct timeval agora;
	gettimeofday(&agora, NULL);
    seconds  = agora.tv_sec  - start.tv_sec;
    useconds = agora.tv_usec - start.tv_usec;
    etime = (((seconds) * 1000 + useconds/1000.0) + 0.5);
    if (etime>100000)
    {
        etime/=1000;
        long tempo=etime;
        std::cout << "\n\nTempo de Processamento: "<<(tempo)/3600 << 'h'<< ((tempo)/60)%60 << 'm'<< (tempo)%60 <<'s' << std::endl;
    }
    else
    {
        std::cout<< setprecision (5) << "\n\nTempo de Processamento: "<< (double)etime/1000 << " segundos" << std::endl;
    }
    #elif (_MSC_VER)
    clock_t now(clock());
    double eltime = (double)(now-start)/CLOCKS_PER_SEC;
    std::cout << "\n\nTempo de Processamento: "<< (double)eltime << " s\t\t ";
    system("pause");
    #endif
    /**/
    //novo
    ostringstream out ;
    string out2 ;
    out << "echo " << ((float)etime)/1000 <<  " >> tempoProc" << numThreads << ".txt";
    //cout << out.str();
    out2=out.str();
    system (out2.c_str());
    /**/
}

void CORE::Run() {

	Model.Initialize( this );
	Dat.Initialize( this );

	strcat( GetFileName( Buf, BUF_SIZE ), INI_EXT );
	Load_INI( Buf );
	Med.Initialize( this );

	strcat( GetFileName( Buf, BUF_SIZE ), DAT_EXT );
	Dat.Create( Buf );

    fflush(stdout);

    #if (__GNUC__)
    gettimeofday(&start, NULL);
    then=start;
    #elif (_MSC_VER)
    start = then = clock();
    #endif
    std::cout << "Completo(%)\tTempo de Processamento\tTempo Restante\n";

    bool fim=false;

    if (Med.qualDetCol!=4)
    {
        if (Med.numeroThreads!=0)
        {
            omp_set_num_threads(Med.numeroThreads);
        }

        #pragma omp parallel if (!Med.tudoSeq)
        {
            while (!fim)
            {
                Med.CalculaForcas();
                /**/
                #pragma omp single
                {
                    if ((Model.DAT_Delay()+Model.Precision >= Dat.Resolution)) Med.FuncoesDeResolucao();
                }
                /**/
                Med.CalculaForcasExternas();
                Med.Integra();
                /**/
                #pragma omp single nowait
                {
                   exportaTempo(Dat.RecTime,Model.Time);
                }
                /**/
                #pragma omp single
                {
                    if ((Model.DAT_Delay() >= Dat.Resolution)) Dat.Write_Frame();
                    if (Model.Time >= Dat.RecTime) fim=true;
                }
            }
        }
    }
    salvaTempo(Med.numeroThreads);

	Save_INI();

	Model.Terminate();
	Med.Terminate();
	Dat.Terminate();
}


void CORE::GetInput( int argc, char* argv[] ) {			// Extrai o nome de arquivo da linha de comando

	if (argc >= 2)
	{
	    bool arquivoGuardado=false;
	    for (int i=1;i<argc;i++)
	    {
	        if ((argv[i][0]=='-'))
	        {

	            switch(argv[i][1])
	            {
	                case 'h':
                            Help();
                            abort();
                            break;

                    case 'P':
                            sscanf (argv[i+1],"%d",&Med.numeroThreads);
                            i++;
                            break;
                    case 'C':
                            switch(argv[i][2])
                            {
                                case 's':
                                        Med.qualDetCampo=3;
                                        break;
                                case 'c':
                                        Med.qualDetCampo=2;
                                        break;
                                case 'm':
                                        Med.qualDetCampo=4;
                                        break;

                                default:
                                        printf("Opção de algoritmo de Campo inválida\n");
                                        abort();
                                        break;
                            }
                            break;
                    case 'D':
                            switch(argv[i][2])
                            {
                                case 's':
                                        Med.qualDetCol=1;
                                        break;
                                case 'n':
                                        Med.qualDetCol=2;
                                        break;
                                case 'c':
                                        Med.qualDetCol=3;
                                        break;
                                case 'm':
                                        Med.qualDetCol=4;
                                        break;
                                default:
                                        printf("Opção de algoritmo de Contato inválida\n");
                                        abort();
                                        break;
                            }
                            break;
                    case 'I':
                            switch(argv[i][2])
                            {
                                case 'r':
                                        Med.qualIntegracao=3;
                                        break;
                                case 'e':
                                        Med.qualIntegracao=1;
                                        break;
                                case 'v':
                                        Med.qualIntegracao=2;
                                        break;
                                default:
                                        printf("Opção de algoritmo de Integração inválida\n");
                                        abort();
                                        break;
                            }
                            break;

                    case 'n':
                            Med.indepSeq=true;
                            break;
                    case 'i':
                            Med.intSeq=true;
                            break;
                    case 'd':
                            Med.detSeq=true;
                            break;
                    case 'f':
                            Med.frontSeq=true;
                            break;
                    case 'M':
                            switch(argv[i][2])
                            {
                                case 's':
                                        Med.qualOpcMola=1;
                                        break;
                                case 'p':
                                        Med.qualOpcMola=2;
                                        break;
                                case 'c':
                                        Med.qualOpcMola=4;
                                        break;
                                default:
                                        printf("Opção de algoritmo de Molas inválida\n");
                                        abort();
                                        break;
                            }
                            break;
                    case 'r':
                            Med.restrSeq=true;
                            break;
                    case 'S':
                            Med.tudoSeq=true;
                            break;
                    default:
                            cout <<"Ignorando entrada desconhecida: " << argv[i] << '\n';
                            break;
	            }

	        }
	        else if(argv[i][0]=='-')
	        {
                            cout <<"Ignorando entrada desconhecida: " << argv[i] << '\n';
	        }
	        else if (!arquivoGuardado)
	        {
	            arquivoGuardado=true;
	            strcpy( FileName, argv[i] );
	        }
	        else
	        {
                 cout <<"Ignorando entrada desconhecida: " << argv[i] << '\n';
	        }
	    }
	}
	else
	{
	    Error("Qual o nome do arquivo??\n");
	   // abort();
    }

    if ((Med.qualOpcMola!=1) && (Med.tudoSeq))
    {
        Med.qualOpcMola=1;
    }

	char* ext = strrchr( FileName, '.' );
	if (ext == NULL) Error("Qual o nome do arquivo??\n");						// Caractere não encontrado
	*ext = '\0';

	Log( "Lendo o arquivo: " , ext, ".INI\n" );
}

char* CORE::GetFileName( char* buf, int size ) {		// Armazena o nome de arquivo no buffer

	return strncpy( buf, FileName, size-1 );
}

void CORE::Log( string s1, string s2, string s3 ) {		// Registra uma ocorrência

	if (!s1.empty()) cout <<  s1 ;
	if (!s2.empty()) cout <<  s2 ;
	if (!s3.empty()) cout <<  s3 ;
}

void CORE::Error( string msg, int row ) {			// Exibe uma mensagem de erro e termina

	if (row == 0) Log( msg, NEWLINE );
	else { snprintf( Buf, BUF_SIZE, "%s (linha %i)\n", msg.c_str(), row ); Log( Buf ); }
	Model.Terminate();
	Med.Terminate();
	Dat.Terminate();
	exit(1);
}

void CORE::Help()
{
    cout << "Ajuda:\n";
    cout << "uso:  ./med <opções> nomedoarquivo\n";
    cout << "Opções:\n";
    cout << "\t-P [num] : Forçar um número de threads\n\n";
    cout << "\t-D : Na detecção de Contato:\n";
    cout << "\t\ts : Usar Screening (default)\n";
    cout << "\t\tc : Screening Collapsed\n";
    cout << "\t\tm : Usar Munjiza-Minato\n";
    cout << "\t\tn : Usar Munjiza-NBS\n\n";
    cout << "\t-C : Nas Forças de Campo:\n";
    cout << "\t\ts : Screening Paralelo \n";
    cout << "\t\tc : Screening Collapsed (default)\n";
    cout << "\t\tm : Munjiza-NBS\n\n";
    cout << "\t-M : Cálculo de Molas:\n";
    cout << "\t\ts : Sequencial\n";
    cout << "\t\tc : Screening (default)\n";
    cout << "\t\tp : Semáforos\n\n";
    cout << "\t-I : Na Integração usar:\n";
    cout << "\t\tr : Runge Kutta 4ta Ordem\n";
    cout << "\t\te : Euler (default)\n";
    cout << "\t\tv : Verlet\n\n";
    cout << "\t-S : Cálculo totalmente sequencial (OpenMP desligado)\n";
    cout << "\t-f : Cálculo de Fronteira Sequencial\n";
    cout << "\t-i : Cálculo de Integração Sequencial\n";
    cout << "\t-d : Cálculo de Detecção de Colisão Sequencial\n";
    cout << "\t-n : Cálculo de Forças Independentes (Arrasto,Gravidade,etc) Sequencial\n";
    cout << "\t-r : Cálculo de Restrições Sequencial\n";
    cout << "\n\n\tObs:.Por padrão tudo é feito em paralelo\n\n";
    cout << "\tExemplo: ./med -S -Dn arquivo.ini (Tudo Sequencial com Munjiza-NBS)\n\n";
}
